package redecolaborativa.sistema;

import java.io.Serializable;
import java.util.GregorianCalendar;
import java.util.LinkedList;
import java.util.List;

import redecolaborativa.enums.BaseDePesquisa;
import redecolaborativa.enums.TipoDeMensagem;
import redecolaborativa.excecoes.AmigoJaExisteException;
import redecolaborativa.excecoes.AssuntoInvalidoException;
import redecolaborativa.excecoes.AtributoInvalidoException;
import redecolaborativa.excecoes.BeneficiadoInvalidoException;
import redecolaborativa.excecoes.CategoriaInvalidaException;
import redecolaborativa.excecoes.DescricaoInvalidaException;
import redecolaborativa.excecoes.DonoInvalidoException;
import redecolaborativa.excecoes.EmprestimoInvalidoException;
import redecolaborativa.excecoes.EnderecoInvalidoException;
import redecolaborativa.excecoes.ItemIndisponivelException;
import redecolaborativa.excecoes.ItemInvalidoException;
import redecolaborativa.excecoes.LoginInvalidoException;
import redecolaborativa.excecoes.NomeInvalidoException;
import redecolaborativa.excecoes.NumeroNegativoException;
import redecolaborativa.excecoes.PalavraChaveInvalidaException;
import redecolaborativa.excecoes.RequisicaoInvalidaException;
import redecolaborativa.excecoes.TipoDeMensagemInvalido;
import redecolaborativa.excecoes.TipoInvalidoException;
import redecolaborativa.excecoes.UsuarioInexistenteException;
import redecolaborativa.gerentes.GerenciadorDeAmigos;
import redecolaborativa.gerentes.GerenciadorDeEmprestimos;
import redecolaborativa.gerentes.GerenciadorDeItens;
import redecolaborativa.gerentes.GerenciadorDeMensagens;
import redecolaborativa.objetos.Emprestimo;
import redecolaborativa.objetos.HistoricoAtividades;
import redecolaborativa.objetos.Item;
import redecolaborativa.objetos.Pedido;
import redecolaborativa.util.Validador;

/**
 * Classe que representa um usuario da rede colaborativa.
 * @author Isabelly Rocha, Lilia Sampaio e Pedro Adelino.
 *
 */
public class Usuario implements Serializable {
	private static final long serialVersionUID = 1L;
	private String nome, login, endereco;
	private HistoricoAtividades historicoDeAtividades;
	private GerenciadorDeAmigos gerenteDeAmigos;
	private GerenciadorDeItens gerenteDeItens;
	private GerenciadorDeEmprestimos gerenteDeEmprestimos;
	private GerenciadorDeMensagens gerenteDeMsg;
	
	/**
	 * Cria um objeto do tipo usuario.
	 * @param Nome do usuario
	 * @param Login do usuario
	 * @param Endereco do usuario
	 * @throws NomeInvalidoException Se o nome do usuario for vazio ou nulo ou se conter digitos
	 * @throws LoginInvalidoException Se o login do usuario for vazio ou nulo
	 * @throws EnderecoInvalidoException Se o endereco do usuario for vazio ou nulo
	 */
	public Usuario(String nome, String login, String endereco) throws NomeInvalidoException, LoginInvalidoException, EnderecoInvalidoException {
		if(nome == null || nome.trim().isEmpty()) {
			throw new NomeInvalidoException("Nome inválido");
		} if(!Validador.validaStringSemNumeros(nome)) {
			throw new NomeInvalidoException("Nome inválido");
		} if(login == null || login.trim().isEmpty()) {
			throw new LoginInvalidoException("Login inválido");
		} if(endereco == null || endereco.trim().isEmpty()) {
			throw new EnderecoInvalidoException("Endereço inválido");
		}
		this.nome = nome;
		this.login = login;
		this.endereco = endereco;
		historicoDeAtividades = new HistoricoAtividades();
		gerenteDeAmigos = new GerenciadorDeAmigos();
		gerenteDeItens = new GerenciadorDeItens();
		gerenteDeEmprestimos = new GerenciadorDeEmprestimos();
		gerenteDeMsg = new GerenciadorDeMensagens();
	}

	/**
	 * Recupera o nome do usuario.
	 * @return O nome do usuario
	 */
	public String getNome() {
		return nome;
	}

	/**
	 * Recupera o login do usuario.
	 * @return O login do usuario
	 */
	public String getLogin() {
		return login;
	}

	/**
	 * Recupera o endereco do usuario.
	 * @return O endereco do usuario
	 */
	public String getEndereco() {
		return endereco;
	}
	
	/**
	 * Recupera o gerente de amigos de um usuario.
	 * @return O gerente de amigos de um usuario.
	 */
	public GerenciadorDeAmigos gerenteDeAmigos() {
		return gerenteDeAmigos;
	}
	
	/**
	 * Recupera o gerente de itens de um usuario.
	 * @return O gerente de itens de um usuario.
	 */
	public GerenciadorDeItens gerenteDeItens() {
		return gerenteDeItens;
	}
	
	/**
	 * Recupera o gerente de emprestimos de um usuario.
	 * @return O gerente de emprestimos de um usuario.
	 */
	public GerenciadorDeEmprestimos gerenteDeEmprestimos() {
		return gerenteDeEmprestimos;
	}
	
	/**
	 * Recupera o gerente de mensagens de um usuario.
	 * @return O gerente de mensagens de um usuario.
	 */
	public GerenciadorDeMensagens gerenteDeMsg() {
		return gerenteDeMsg;
	}
	
	/**
	 * Recupera o historico de atividades de um usuario.
	 * @return O historico de atividades de um usuario.
	 */
	public HistoricoAtividades getHistoricoDeAtividades() {
		return historicoDeAtividades;
	}
	
	/**
	 * Cadastra um item e adiciona na lista de itens do Usuario.
	 * @param nome Nome do item 
	 * @param categoria Categorias do item
	 * @param descricao Descricao do item
	 * @throws NomeInvalidoException Se o nome do item for nulo ou vazio
	 * @throws CategoriaInvalidaException Se a categoria do item for nulo ou vazio
	 * @throws DescricaoInvalidaException Se a descricao do item for nulo ou vazio
	 * @throws DonoInvalidoException Se o dono do item for nulo
	 */
	public void cadastrarItem(String nome, String categoria, String descricao) throws NomeInvalidoException, CategoriaInvalidaException, DescricaoInvalidaException, DonoInvalidoException, InterruptedException {
		if(categoria == null || categoria.toString().trim().equals("")) {
			throw new CategoriaInvalidaException("Categoria inválida");
		}
		gerenteDeItens.adiciona(nome, formataCategoria(categoria), descricao, this);
	}

	/**
	 * Remove um item da lista de itens do Usuario.
	 * @param Item a ser removido
	 * @throws ItemInvalidoException 
	 * @throws ItemIndisponivelException 
	 */
	public void apagarItem(Item item) throws ItemInvalidoException, ItemIndisponivelException {
		gerenteDeItens.remove(item);
	}

	/**
	 * Requisita a amizade de um usuario adicionando esse usuario a lista de solicitacoes de amizade do Usuario.
	 * @param Usuario a ser requisitado
	 * @throws AmigoJaExisteException 
	 * @throws RequisicaoInvalidaException 
	 */
	public void requisitarAmizade(Usuario requisitado) throws AmigoJaExisteException, RequisicaoInvalidaException {
		gerenteDeAmigos.requisitaAmizade(this, requisitado);
	}

	/**
	 * Aprova a amizade requisitada adicionando na lista de amigos e removendo na lista de solicitacoes.
	 * @param Usuario a ser aprovado
	 * @throws AmigoJaExisteException 
	 * @throws RequisicaoInvalidaException 
	 * @throws InterruptedException 
	 */
	public void aprovarAmizade(Usuario usuario) throws AmigoJaExisteException, RequisicaoInvalidaException, InterruptedException {
		gerenteDeAmigos.adiciona(this, usuario);
		Thread.sleep(1);
		String atividadeAprovante = String.format("%s e %s são amigos agora", nome, usuario.getNome());
		String atividadeAprovado = String.format("%s e %s são amigos agora", usuario.getNome(), nome);
		historicoDeAtividades.adicionaAtividade(atividadeAprovante, new GregorianCalendar());
		usuario.getHistoricoDeAtividades().adicionaAtividade(atividadeAprovado, new GregorianCalendar());
	}

	/**
	 * Rejeita a amizade que foi requisitada removendo da lista de solicitacoes.
	 * @param Usuario a ser rejeitado
	 */
	public void rejeitarAmizade(Usuario usuario) {
		gerenteDeAmigos.rejeitarAmizade(usuario);
	}

	/**
	 * Remove um amigo, removendo da lista de amigos.
	 * @param Usuario a ser removido
	 * @throws UsuarioInexistenteException 
	 */
	public void desfazerAmizade(Usuario usuario) throws UsuarioInexistenteException  {
		gerenteDeAmigos.remove(usuario);
		usuario.gerenteDeAmigos().remove(this);
	}

	/**
	 * Requisita um item de um amigo, adicionando o item na lista de solicitacoes de emprestimo do amigo e enviando uma mensagem
	 * default relacionado ao item que quer emprestado. 
	 * @param Item a ser requisitado
	 * @param Tempo que o Usuario quer ficar com o item
	 * @throws ItemIndisponivelException Se o item nao estiver disponivel
	 * @throws BeneficiadoInvalidoException Se o usuario beneficiado for null
	 * @throws ItemInvalidoException Se o item requisitado for null
	 * @throws NumeroNegativoException Se o tempo for negativo
	 */
	public void requisitarItem(Item item, int tempoPrevisto) throws ItemIndisponivelException, BeneficiadoInvalidoException, ItemInvalidoException, NumeroNegativoException, Exception { 
		gerenteDeEmprestimos.pedeItemEmprestado(this, item, tempoPrevisto);
		String assunto = String.format("Empréstimo do item %s a %s",  item.getNome(), getNome());
		String descricao = String.format("%s solicitou o empréstimo do item %s", getNome(), item.getNome());
		enviaMensagem(TipoDeMensagem.NEGOCIACAO, assunto, descricao, item.getDono());
	}

	/**
	 * Aceita a solicitacao de item, iniciando o tempo que o beneficiado vai ficar com o item, adicionando o emprestimo na lista de 
	 * emprestimos, e o item emprestado na lista de itens adquiridos e removendo o emprestimo da lista de solicitacoes de emprestimo.
	 * @param Item a ser aceito
	 * @throws DonoInvalidoException 
	 * @throws EmprestimoInvalidoException 
	 * @throws ItemInvalidoException 
	 * @throws DescricaoInvalidaException 
	 * @throws AssuntoInvalidoException 
	 * @throws TipoDeMensagemInvalido 
	 * @throws InterruptedException 
	 */
	public void aceitarSolicitacaoDeItem(Item item) throws DonoInvalidoException, EmprestimoInvalidoException, ItemInvalidoException, TipoDeMensagemInvalido, AssuntoInvalidoException, DescricaoInvalidaException, InterruptedException {
		if(!this.equals(item.getDono())) {
			throw new DonoInvalidoException("O empréstimo só pode ser aprovado pelo dono do item");
		} if(gerenteDeItens.pesquisaItemPorID(String.valueOf(item.getID())) == null) {
			throw new EmprestimoInvalidoException("Requisição de empréstimo inexistente");
		}
		gerenteDeEmprestimos.aceitaSolicitacaoDeItem(this, item);
	}

	/**
	 * Rejeita solicitacao de emprestimo do item, removendo o emprestimo da lista de solicitacao de emprestimo do usuario. 
	 * @param Item a ser rejeitado
	 */
	public void rejeitarSolicitacaoDeItem(Item item) {
		gerenteDeEmprestimos.rejeitaSolicitacaoDeItem(item);
	}

	/**
	 * Notifica o dono do item que se quer devolver o item, adicionando o item na lista de notificacoes de devolucoes.
	 * @param Item a ser devolvido 
	 * @throws ItemInvalidoException 
	 */
	public void devolverItem(Item item) throws ItemInvalidoException  {
		gerenteDeEmprestimos.devolveItem(item);
	}

	/**
	 * Confirma a devolucao do item, finalizando o emprestimo e removendo o item da lista de notificacoes de devolucoes.
	 * @param Item a ser confirmado a devolucao
	 * @throws EmprestimoInvalidoException 
	 * @throws DescricaoInvalidaException 
	 * @throws AssuntoInvalidoException 
	 * @throws TipoDeMensagemInvalido 
	 * @throws InterruptedException 
	 */
	public void confirmaDevolucao(Item item) throws EmprestimoInvalidoException, TipoDeMensagemInvalido, AssuntoInvalidoException, DescricaoInvalidaException, InterruptedException {
		Thread.sleep(1);
		gerenteDeEmprestimos.confirmaDevolucao(item);
		String atividade = String.format("%s confirmou o término do empréstimo do item %s", nome, item.getNome());
		historicoDeAtividades.adicionaAtividade(atividade, new GregorianCalendar());
	}

	/**
	 * Requisita a devolucao de um item que esta emprestado, adicionando o item na lista de requisicoes de devolucao.
	 * @param Item emprestado a ser requisitado
	 * @throws RequisicaoInvalidaException 
	 * @throws DescricaoInvalidaException 
	 * @throws AssuntoInvalidoException 
	 * @throws TipoDeMensagemInvalido 
	 * @throws ItemInvalidoException 
	 */
	public void requisitarDevolucaoDeItem(Item item) throws RequisicaoInvalidaException, TipoDeMensagemInvalido, AssuntoInvalidoException, DescricaoInvalidaException {
		Emprestimo empRequisitado = gerenteDeEmprestimos.requisitaDevolucaoDeItem(item);
		String assunto = String.format("Empréstimo do item %s a %s", item.getNome(), empRequisitado.getBeneficiado().getNome());
		String descricao = String.format("%s solicitou a devolução do item %s", nome, item.getNome());
		enviaMensagem(TipoDeMensagem.NEGOCIACAO, assunto, descricao, empRequisitado.getBeneficiado());
	}

	/**
	 * Regista o intesse de um Usuario por um item de seu amigo, se o item nao estiver disponivel o Usuario eh adicionado a lista
	 * de interessados do item	
	 * @param Item pelo qual o usuario esta interessado
	 * @return True se voce foi adicionado a lista de interessado, ou false caso contrario
	 * @throws DonoInvalidoException Se voce for o dono do item
	 */
	public void registraInteresseEmItem(Item item) throws DonoInvalidoException, Exception {
		gerenteDeItens.registraInteresse(item, this);
	}

	/**
	 * Pesquisa um item, que pode ser pela data de criacao do item ou pela reputacao do usuario e por ordem(Crescente ou Decrescente)
	 * de data ou reputacao do usuario.
	 * @param Palavra a ser pesquisada
	 * @param A base da pesquisa (Se por data ou reputacao do usuario)
	 * @param A ordem da pesquisa (Data ou Reputacao, na ordem crescendo ou decrescente)
	 * @return A lista dos itens encontrados
	 * @throws PalavraChaveInvalidaException Se a chave for nula ou vazia. 
	 * @throws TipoInvalidoException Se o tipo de ordenacao ou o criterio de ordenacao forem nulos, vazios ou inexistentes.
	 * @throws AtributoInvalidoException Se o atributo a ser pesquisado é nulo, vazio ou inexistente.
	 */
	public String pesquisaItem(String palavraChave, String atributo, String tipoOrdenacao, String criterio) throws PalavraChaveInvalidaException, TipoInvalidoException, AtributoInvalidoException {
		if(palavraChave == null || palavraChave.trim().equals("")) {
			throw new PalavraChaveInvalidaException("Chave inválida");
		} if(atributo == null || atributo.trim().equals("")) {
			throw new AtributoInvalidoException("Atributo inválido");
		} if(tipoOrdenacao == null || tipoOrdenacao.trim().equals("")) {
			throw new TipoInvalidoException("Tipo inválido de ordenação");
		} if(criterio == null || criterio.trim().equals("")) {
			throw new TipoInvalidoException("Critério inválido de ordenação");		
		} if(!atributo.equals("nome") && !atributo.equals("descricao") && !atributo.equals("categoria")) {
			throw new AtributoInvalidoException("Atributo inexistente");
		} if(!tipoOrdenacao.equalsIgnoreCase("crescente") && !tipoOrdenacao.equalsIgnoreCase("decrescente")) {
			throw new TipoInvalidoException("Tipo de ordenação inexistente");
		} if (!criterio.equalsIgnoreCase(BaseDePesquisa.DATACRIACAO.name()) && !criterio.equalsIgnoreCase(BaseDePesquisa.REPUTACAO.name())){
			throw new TipoInvalidoException("Critério de ordenação inexistente");
		}
		return gerenteDeItens.pesquisaItens(this, palavraChave, atributo, criterio, tipoOrdenacao);
	}	

	/**
	 * Envia uma mensagem para um Usuario.
	 * @param Mensagem a ser enviada
	 * @param Amigo ao qual quer enviar uma mensagem
	 * @throws DescricaoInvalidaException 
	 * @throws AssuntoInvalidoException 
	 * @throws TipoDeMensagemInvalido 
	 */
	public String enviaMensagem(TipoDeMensagem tipo, String assunto, String descricao, Usuario destinatario) throws TipoDeMensagemInvalido, AssuntoInvalidoException, DescricaoInvalidaException {
		return gerenteDeMsg.enviaMensagem(tipo, assunto, descricao, destinatario);
	}

	/**
	 * Metodo onde o usuario publica um pedido
	 * @param nomeItem Nome do item que ele quer pedir
	 * @param descricao Descricao do item que ele quer pedir
	 * @throws NomeInvalidoException 
	 * @throws DescricaoInvalidaException 
	 * @throws InterruptedException 
	 * @throws Exception 
	 */
	public void publicarPedido(String nomeItem, String descricao) throws NomeInvalidoException, DescricaoInvalidaException, InterruptedException  {
		if (nomeItem == null || nomeItem.trim().isEmpty()) {
			throw new NomeInvalidoException("Nome inválido");
		} if (descricao == null || descricao.trim().isEmpty()) {
			throw new DescricaoInvalidaException("Descrição inválida");
		} 
		Thread.sleep(1);
		Pedido pedido = new Pedido(this, nomeItem);
		if(!this.getHistoricoDeAtividades().retornaTodasAsPublicacoes(this).contains(pedido.toString())) {
			this.getHistoricoDeAtividades().adicionaAtividade(pedido.toString(), new GregorianCalendar());
		}
	}
	
	/**
	 * Metodo que republica um pedido
	 * @param quemPublicou Usuario que publicou o pedido
	 * @param nomeItem Nome do item que foi publicado
	 * @throws Exception
	 */
	public void rePublicarPedido(String quemPublicou, String nomeItem) throws Exception {
		if (nomeItem == null || nomeItem.trim().isEmpty()) {
			throw new Exception("Nome inválido");
		} 
		Thread.sleep(1);
		String pedido = String.format("%s precisa do item %s", quemPublicou, nomeItem);
		historicoDeAtividades.adicionaAtividade(pedido, new GregorianCalendar());
	}

	/**
	 * Metodo que oferece um item a um usuario que fez um pedido
	 * @param nomeUsuario Nome do usuario que fez o pedido
	 * @param nomeItem Nome do item que quer oferecer
	 * @throws DescricaoInvalidaException 
	 * @throws AssuntoInvalidoException 
	 * @throws TipoDeMensagemInvalido 
	 * @throws ItemInvalidoException 
	 * @throws Exception 
	 */
	public void oferecerItem(Usuario usuario, String nomeItem) throws NomeInvalidoException, ItemInvalidoException, TipoDeMensagemInvalido, AssuntoInvalidoException, DescricaoInvalidaException{
		Item itemOferecido = gerenteDeItens.oferecerItem(usuario, nomeItem);
		String assunto = "O usuário " + nome + " ofereceu o item " + itemOferecido.getNome();
		String descricao = "Item oferecido: " + itemOferecido.getNome() + " - " + itemOferecido.getDescricao(); 
		enviaMensagem(TipoDeMensagem.OFFTOPIC, assunto, descricao, usuario);
	}
	
	/**
	 * Metodo que formata a categoria do item
	 * @param categoria As categorias do item
	 * @return Lista contendo as categorias do item
	 */
	public List<String> formataCategoria (String categoria){
		List<String> categorias = new LinkedList<String>();
		String[] s = categoria.split(" ");
		for(int i = 0; i < s.length; i++){
			categorias.add(s[i]);
		} 
		return categorias;
	}
	
	/**
	 * Compara dois objetos do tipo Usuario.
	 * @param Objeto Usuario a ser comparado com esse objeto
	 * @return True se possuirem todos atributos iguais, ou false caso contrario
	 */
	@Override
	public boolean equals(Object objeto) {
		if (!(objeto instanceof Usuario )) return false;
		Usuario usuario = (Usuario) objeto;
		return (nome.equals(usuario.getNome()) && login.equals(usuario.getLogin()) && endereco.equals(usuario.getEndereco()));
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((endereco == null) ? 0 : endereco.hashCode());
		result = prime * result + ((login == null) ? 0 : login.hashCode());
		result = prime * result + ((nome == null) ? 0 : nome.hashCode());
		return result;
	}
	
	/**
	 * Compara usuarios por emprestimos finalizados.
	 * @param Usuario a ser comparado com esse
	 * @return A diferenca da quantidade de itens emprestados pelos dois Usuarios
	 */
	public int compareTo(Usuario usuario) {
		return gerenteDeEmprestimos.emprestimosFinalizados() - usuario.gerenteDeEmprestimos.emprestimosFinalizados();
	}
}